package com.demo.security.filter;

import com.demo.security.handler.extract.JwtHeaderTokenExtractor;
import com.demo.security.token.JwtAuthenticationToken;
import com.demo.security.token.Token;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 用户请求任何资源接口时,会被此拦截器拦截
 * 本拦截器主要是验证JWT token是否合法.JWT TOKEN
 * 如果合法,则通过token获取到认证,并将认证放到SecurityContextHolder
 * 如果非法,则返回错误信息
 * 本拦截器还会捕获认证失败异常.并将失败信息返回给用户.
 * @author molong
 * @since 2024/5/24
 */
public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    /**
     * TOKEN拾取器
     */
    private final JwtHeaderTokenExtractor tokenExtractor = new JwtHeaderTokenExtractor();

    public JwtAuthenticationFilter(AuthenticationFailureHandler failureHandler, AuthenticationManager authenticationManager) {
        super(AnyRequestMatcher.INSTANCE);
        setAuthenticationFailureHandler(failureHandler);
        setAuthenticationManager(authenticationManager);
    }

    /**
     * 具体认证方法
     *
     * @param request  the request
     * @param response the response
     * @return the authenticated user token, or null if authentication is incomplete.
     * @throws AuthenticationException if authentication fails.
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        Token token = tokenExtractor.extract(request);
        return getAuthenticationManager().authenticate(new JwtAuthenticationToken(token));
    }

    /**
     * 认证成功后,将TOKEN放到上下文中.以方便接口获取用户
     *
     * @param request    request
     * @param response   response
     * @param chain      拦截chain
     * @param authResult the object returned from the <tt>attemptAuthentication</tt>
     *                   method.
     * @throws IOException      IOException
     * @throws ServletException ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                            FilterChain chain, Authentication authResult) throws IOException, ServletException {
        SecurityContextHolder.getContext().setAuthentication(authResult);
        chain.doFilter(request, response);
    }

    /**
     * 认证失败处理,清理掉用户信息,并返回错误信息
     *
     * @param request  request
     * @param response response
     * @param failed   失败异常
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request,
                                              HttpServletResponse response,
                                              AuthenticationException failed) throws IOException, ServletException {
        SecurityContextHolder.clearContext();
        getFailureHandler().onAuthenticationFailure(request, response, failed);
    }
}